
#define LOG_TAG "bus"

#include "wa_bus.h"
#include <string.h>
#include <stdlib.h>
#include "logs.h"
#include "path.h"
#include "cJSON_ext.h"
#include "misc.h"

#define BUS_TYPE_ROT "rot"
#define BUS_TYPE_RTU "rtu"
#define BUS_TYPE_TCP "tcp"


wa_bus_info_t * parse_bus_JSON_cfg_A(cJSON*  bus_obj)
{
    wa_bus_info_t * bus_info = NULL;

    const char* name_str = cJSONx_GetObjectString(bus_obj, "name");
    if(name_str == NULL || name_str[0] == 0)
        return NULL;

    const char* type_str = cJSONx_GetObjectString(bus_obj, "type");

    bus_info = (wa_bus_info_t *) malloc(sizeof(*bus_info));
    if(bus_info == NULL)
    {
        LOG_RETURN(NULL);
    }
    memset(bus_info, 0, sizeof(*bus_info));

    if(type_str == NULL || strcmp(type_str,BUS_TYPE_RTU)==0)
    {
        bus_info->bus_type= T_Bus_Serials;
        const char  * tty = cJSONx_GetObjectString (bus_obj, "tty");
        int baud = cJSONx_GetObjectNumber(bus_obj, "baud", 9600);
        if(tty == NULL)
        {
            WARNING("parse_bus_JSON_cfg_A Error!. tty invalid");
            goto end;
        }
        bus_info->serials_addr.serials_port_name = strdup(tty);
        bus_info->serials_addr.baud_rate = baud;
        const char * mode = cJSONx_GetObjectString(bus_obj, "mode");
        if(mode) bus_info->serials_addr.mode = strdup(mode);
    }
    else if(strcmp(type_str, BUS_TYPE_ROT)==0 || strcmp(type_str, BUS_TYPE_TCP)==0)
    {
        bus_info->bus_type = (strcmp(type_str, BUS_TYPE_ROT)==0)?T_Bus_RoT:T_Bus_TCP;
        const char * ip = cJSONx_GetObjectString(bus_obj, "ip");
        int port = cJSONx_GetObjectNumber(bus_obj, "port", 0);

        if(ip == NULL || port <= 0)
        {
            WARNING( "parse_bus_JSON_cfg_A ip or port Error!");
            goto end;
        }
        bus_info->tcp_addr.ip = strdup(ip);
        bus_info->tcp_addr.port = port;
    }
    else
    {
        WARNING( "parse_bus_JSON_cfg_A:  unsupported type [%s]!", type_str);
        goto end;
    }

    bus_info->bus_name = strdup(name_str);
    bus_info->timeout_s = cJSONx_GetObjectNumber(bus_obj, "waits", 1);
    bus_info->timeout_us = cJSONx_GetObjectNumber(bus_obj, "waitus", 0);
    bus_info->close_bus_idle_ms = cJSONx_GetObjectNumber(bus_obj, "close_bus_idle_ms", 0);
	bus_info->min_wait_ms = cJSONx_GetObjectNumber(bus_obj, "min_wait_ms", 0);

    return bus_info;
    
end:
    if(bus_info)
    {
        wa_bus_info_free(bus_info);
    }
    return NULL;
}


wa_bus_info_t* wa_find_factory_bus_info_A(const char * bus_name){
    wa_bus_info_t * bus_info = NULL;
    char path[256];
    char* path_env = getenv("WA_BOARD_BASE_DIR");
    snprintf(path, sizeof(path), "%s/board/factory_IO.JSON",
        path_env ? path_env : "/opt/wa-board");
    cJSON* f_root = load_json_file_A(path);
    cJSON * COM;
    if (cJSON_IsObject(f_root) && (COM = cJSON_GetObjectItem(f_root, "COM")) && cJSON_IsArray(COM))
    {
        cJSON * com_obj;
        cJSON_ArrayForEach(com_obj, COM)
        {
            const char* name = cJSONx_GetObjectString(com_obj, "name");
            const char* device = cJSONx_GetObjectString(com_obj, "device");
            if (name && device && strcmp(name, bus_name) == 0)
            {
                WARNING("Load factory serial [%s] for bus %s, cfg: %s", device, bus_name, path);
                bus_info = (wa_bus_info_t*)malloc(sizeof(wa_bus_info_t));
                memset(bus_info, 0, sizeof(wa_bus_info_t));
                bus_info->bus_name = strdup(bus_name);
                bus_info->serials_addr.serials_port_name = strdup(device);
                bus_info->serials_addr.baud_rate = 9600;
                bus_info->bus_type = T_Bus_Serials;
                break;
            }
        }
    }
    cJSON_Delete(f_root);
    return bus_info;
}



wa_bus_info_t* wa_load_bus_config_A(const char * bus_name)
{
    cJSON* root;
    cJSON* bus_cfg = NULL;
    wa_bus_info_t* bus_info = NULL;
    char ini_path[PATH_LEN] = { 0 };
    char buf[256];
    snprintf(ini_path, sizeof(ini_path), 
        "%s/local-cfg/bus.cfg",
        get_plc_framework_root(buf, sizeof(buf)));
    
    if ((root = load_json_file_A(ini_path)) == NULL)
    {
        // try to see if it is defined by the factory info before fail it
        return wa_find_factory_bus_info_A(bus_name);
    }

    bus_cfg = cJSON_GetObjectItem(root, "bus");
    if(!cJSON_IsArray(bus_cfg))
    {
        cJSON_Delete(root);
        return NULL;
    }
    cJSON *bus_obj;
    cJSON_ArrayForEach(bus_obj, bus_cfg)
    {
        const char* name = cJSONx_GetObjectString(bus_obj, "name");
        if(name == NULL) continue;
        if(strcmp(name, bus_name) != 0) continue;

        bus_info = parse_bus_JSON_cfg_A(bus_obj);
        if (bus_info)
        {
            WARNING2("Loaded bus [%s], type:%d", name, bus_info->bus_type);
        }
        break;
    }
    cJSON_Delete(root);
    if(bus_info == NULL)
        return wa_find_factory_bus_info_A(bus_name);
    else
        return bus_info;
}

bool check_bus_info_valid(wa_bus_info_t *info)
{
    if(info == NULL) return false;
    if(info->bus_name == NULL) return false;
    if(info->bus_type == T_Bus_Serials)
    {
        if(info->serials_addr.serials_port_name == NULL) return false;
    }
    else if(info->bus_type == T_Bus_RoT || info->bus_type == T_Bus_TCP)
    {
        if(info->tcp_addr.ip == NULL) return false;
    }
    else if(info->bus_type == T_Bus_Device)
    {
        if(info->device_addr.device_name == NULL) return false;
    }
    else if(info->bus_type == T_Bus_Generial)
    {
        if(info->general_addr == NULL) return false;
    }
    else
    {
        WARNING("didn't support this modbus type: %d", info->bus_type);
        return false;
    }
    return true;
}


void clean_bus_info(wa_bus_info_t *info)
{
    if(info->bus_name) 
    {
        free(info->bus_name);
        info->bus_name = NULL;
    }
    if(info->bus_type == T_Bus_Serials)
    {
        if(info->serials_addr.serials_port_name)
            free(info->serials_addr.serials_port_name);
        if(info->serials_addr.mode)
            free(info->serials_addr.mode);
        info->serials_addr.mode = NULL;
        info->serials_addr.serials_port_name = NULL;
    }
    else if(info->bus_type == T_Bus_RoT || info->bus_type == T_Bus_TCP )
    {
        if(info->tcp_addr.ip) free(info->tcp_addr.ip);
        info->tcp_addr.ip = NULL;
    }
    else if(info->bus_type == T_Bus_Device)
    {
        if(info->device_addr.device_name) free(info->device_addr.device_name);
        info->device_addr.device_name = NULL;
    }
    else if(info->bus_type == T_Bus_Generial)
    {
        if(info->general_addr) free(info->general_addr);
        info->general_addr = NULL;
    }
    else
    {
        WARNING("didn't support this modbus type: %d", info->bus_type);
    }
}


void wa_bus_info_free(wa_bus_info_t * info)
{
    clean_bus_info(info);
    free(info);
}

bool wa_bus_compare_info(wa_bus_info_t *info1, wa_bus_info_t *info2)
{
    if(!check_bus_info_valid(info1) || !check_bus_info_valid(info2))
        return false;

    if(info1->bus_type != info2->bus_type)
        return false;

    if(info1->bus_type == T_Bus_Serials)
    {
        if(strcmp(info1->serials_addr.serials_port_name,
                info2->serials_addr.serials_port_name) == 0)
            return true;
    }
    else if(info1->bus_type == T_Bus_RoT)
    {
        if(strcmp(info1->tcp_addr.ip, info2->tcp_addr.ip) == 0 &&
                info1->tcp_addr.port == info2->tcp_addr.port)
            return true;
    }
    else if(info1->bus_type == T_Bus_Device)
    {
        if(strcmp(info1->device_addr.device_name, info2->device_addr.device_name) == 0)
            return true;
    }
    else if(info1->bus_type == T_Bus_Generial)
    {
        if(strcmp(info1->general_addr, info2->general_addr) == 0)
            return true;
    }
    else
    {
        WARNING("didn't support this modbus type: %d", info1->bus_type);
    }

    return false;
}


void copy_bus_info(wa_bus_info_t *src, wa_bus_info_t *dest)
{
    dest->bus_type = src->bus_type;
    dest->close_bus_idle_ms = src->close_bus_idle_ms;
    if(src->bus_type == T_Bus_Serials)
    {
        dest->serials_addr.serials_port_name = src->serials_addr.serials_port_name ? strdup(src->serials_addr.serials_port_name) : NULL;
        dest->serials_addr.baud_rate = src->serials_addr.baud_rate;
        return ;
    }
    else if(src->bus_type  == T_Bus_RoT)
    {
        dest->tcp_addr.ip = src->tcp_addr.ip ? strdup(src->tcp_addr.ip) : NULL;
        dest->tcp_addr.port =src->tcp_addr.port;
        return;
    }
    WARNING("didn't support this modbus type: %d", src->bus_type);
}




char * wa_plugin_load_device_cfg_A(const char * plugin_name)
{
    cJSON* root = NULL;
    cJSON * plugin_devices = NULL;
    cJSON* devices;
    char ini_path[PATH_LEN];
    char buf[256];
    snprintf(ini_path, sizeof(ini_path), 
        "%s/local-cfg/vdev/vdevs.cfg",
        get_plc_framework_root(buf, sizeof(buf)));
    
    if ((root = load_json_file_A(ini_path)) == NULL)
    {
        return NULL;
    }

    devices = cJSON_GetObjectItem(root, "devices");
    if(!cJSON_IsArray(devices)) goto end;

    plugin_devices = cJSON_CreateArray();
    if(plugin_devices == NULL) goto end;

    cJSON * device;
    cJSON_ArrayForEach(device, devices)
    {
        const char * plugin = cJSONx_GetObjectString(device, "plugin");
        if(plugin &&  strcmp(plugin, plugin_name) == 0)
            cJSON_AddItemReferenceToArray(plugin_devices, device);
    }

end:
    char * ret = NULL;
    if(plugin_devices)
    {
        ret = cJSON_Print(plugin_devices);
        cJSON_Delete(plugin_devices);
    }

    cJSON_Delete(root);
    return ret;
}


void wa_plugin_free_device_info(wa_plugin_device_info_t * devices, int device_num)
{
    if(devices == NULL) return;

    for(int i=0; i < device_num ;i ++)
    {
        wa_plugin_device_info_t * info = &devices[i];
        if(info->bus_info) wa_bus_info_free(info->bus_info);
        if(info->device_name) free(info->device_name);
        if(info->plugin_name) free(info->plugin_name);
        if(info->URI) free(info->URI);
        if(info->custom_cfg) cJSON_free(info->custom_cfg);
    }

    free(devices);
}


wa_plugin_device_info_t * wa_plugin_parse_device_info_A(const char * device_cfg, int * device_num)
{
    *device_num = 0;
    cJSON * devices = cJSON_Parse(device_cfg);
    if(!devices) return NULL;

    if(!cJSON_IsArray(devices) || cJSON_GetArraySize(devices) == 0)
    {
        cJSON_Delete(devices);
        return NULL;
    }

    wa_plugin_device_info_t * info = (wa_plugin_device_info_t *) malloc_z(cJSON_GetArraySize(devices) * sizeof(wa_plugin_device_info_t));
    if(info == NULL)
    {
        cJSON_Delete(devices);
        return NULL;
    }

    int num = 0;
    cJSON * device;
    cJSON_ArrayForEach(device, devices)
    {
        const char * plugin = cJSONx_GetObjectString(device, "plugin");
        const char * device_name = cJSONx_GetObjectString(device, "alias");
        const char * uri = cJSONx_GetObjectString(device, "uri");
        const char * ip = cJSONx_GetObjectString(device, "ip");
        int port = cJSONx_GetObjectNumber(device, "port", -1);
        const char * serial_bus = cJSONx_GetObjectString(device, "serial_bus");
        const char * device_type = cJSONx_GetObjectString(device, "device_type");
        int slave_id = cJSONx_GetObjectNumber(device, "slave_id", -1);
        cJSON * custom = cJSON_GetObjectItem(device, "custom");

        if(plugin == NULL || device_name == NULL || uri == NULL || device_type == NULL)
            continue;
        wa_bus_info_t* bus_info = NULL;
        if(strcmp(device_type, "SERIAL_BUS") == 0)
        {
            if(serial_bus == NULL) continue;
            bus_info = wa_load_bus_config_A(serial_bus);
        }
        else if(strcmp(device_type, "IP_ADDR") == 0)
        {
            if(ip == NULL || port <= 0) continue;
            bus_info = (wa_bus_info_t*) malloc_z(sizeof(wa_bus_info_t));
            if(bus_info == NULL) break;

            bus_info->bus_name = strdup(ip);
            bus_info->bus_type = T_Bus_TCP;
            bus_info->tcp_addr.ip = strdup(ip);
            bus_info->tcp_addr.port = port;
            printf("bus_info: %p, tcp_addr: %p, offset: %ld\n", bus_info, &bus_info->tcp_addr, (char *)&bus_info->tcp_addr - (char*)bus_info);
        }

        if(bus_info)
        {
            wa_plugin_device_info_t * current = &info[num];
            num ++;
            current->bus_info = bus_info;
            current->device_name = strdup(device_name);
            current->plugin_name = strdup(plugin);
            current->URI = strdup(uri);
            current->device_ID = slave_id;
            current->custom_cfg = custom ? cJSON_PrintUnformatted(custom) : NULL;
        }
    }

    if(num == 0)
    {
        free(info);
        info = NULL;
    }

    *device_num = num;
    return info;
}



wa_plugin_device_info_t * wa_plugin_load_device_info_A(const char * plugin_name, int * device_num)
{
    *device_num = 0;
    char * cfg = wa_plugin_load_device_cfg_A(plugin_name);
    if(cfg == NULL)
        return NULL;

    wa_plugin_device_info_t * info = wa_plugin_parse_device_info_A(cfg, device_num);
    cJSON_free(cfg);
    return info;
}
